#include <cmath>
#include <cassert>
#include <cmath>
#include "SRGLinc.h"
#include "SRGLMachine.h"
#include "SRPrimitiveModel.h"

#include <mio.h>

using std::vector;
using namespace matsu;

namespace sora
{
	namespace gl
	{
		void printGLString(const char *name, GLenum s) 
		{
			const char *v = (const char *) glGetString(s);
			LOGI("GL %s = %s\n", name, v);
		}

		void checkGlError(const char* op) 
		{
			for (GLint error = glGetError(); error; error = glGetError()) 
			{
				LOGI("after %s() glError (0x%x)\n", op, error);
			}
		}
	}
#if _GLES1_
	namespace gles1
	{
		GLMachine::GLMachine()
		{
			reset();
		}
		GLMachine::~GLMachine()
		{
		}
		GLMachine &GLMachine::machine()
		{
			static GLMachine m;
			return m;
		}

		void GLMachine::addVertex(float x, float y, float z)
		{
			//위치 설정
			matsu::vec3 pos(x, y, z);

			//색 설정
			float c[4];
			glGetFloatv(GL_CURRENT_COLOR, c);
			matsu::vec4 color(c);

			Vertex3 vertex(pos, color, texCoord_);
			vertexVec_.push_back(vertex);
		}
		void GLMachine::setTexCoord(float s, float t)
		{
			texCoord_.x = s;
			texCoord_.y = t;
			texCoordEnable_ = true;
		}

		void GLMachine::glBegin(GLenum mode)
		{
			drawMode_ = mode;
		}

		void GLMachine::glesDraw()
		{
			glDrawArrays(drawMode_, 0, vertexVec_.size());
		}
		void GLMachine::soraDraw()
		{
			//gl_quads만 흉내내자
			assert(drawMode_ == SORA_GL_QUADS);

			//gl_quads의 조건
			assert(vertexVec_.size() % 4 == 0);

			//index list를 사용
			vector<GLushort> indexList;
			for(std::size_t i = 0 ; i < vertexVec_.size() / 4 ; i++)
			{
				int a = i * 4;
				int b = a + 1;
				int c = a + 2;
				int d = a + 3;

				indexList.push_back(a);
				indexList.push_back(b);
				indexList.push_back(c);

				indexList.push_back(a);
				indexList.push_back(c);
				indexList.push_back(d);
			}

			glDrawElements(GL_TRIANGLES, indexList.size(), GL_UNSIGNED_SHORT, &indexList[0]);
		}

		void GLMachine::glEnd()
		{
			assert(drawMode_ != -1 && "must use glBegin");

			//이전 상태 저장. 후에 복구하기
			GLboolean vertexArrayEnable = glIsEnabled(GL_VERTEX_ARRAY);
			GLboolean colorArrayEnable = glIsEnabled(GL_COLOR_ARRAY);
			GLboolean textureCoordArrayEnable = glIsEnabled(GL_TEXTURE_COORD_ARRAY);

			//draw...index를 사용해서 에뮬레이팀하니까 opengl 기본설정은 동일하다
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_COLOR_ARRAY);
			if(isTexCoordEnabled())
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);

			glVertexPointer(3, GL_FLOAT, sizeof(Vertex3), &vertexVec_[0].pos[0]);
			glColorPointer(4, GL_FLOAT, sizeof(Vertex3), &vertexVec_[0].color[0]);
			if(isTexCoordEnabled())
				glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex3), &vertexVec_[0].texCoord[0]);

			//draw vertex
			switch (drawMode_) {
				case SORA_GL_QUADS:
					soraDraw();
					break;
				default:
					glesDraw();
					break;
			}

			//restore state
			if(vertexArrayEnable == false)
				glDisableClientState(GL_VERTEX_ARRAY);
			if(colorArrayEnable == false)
				glDisableClientState(GL_COLOR_ARRAY);
			if(textureCoordArrayEnable == false)
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);

			//clear all property
			reset();
		}

		void GLMachine::reset()
		{
			vertexVec_.clear();
			drawMode_ = -1;

			texCoord_.x = 0;
			texCoord_.y = 0;
			texCoordEnable_ = false;
		}

		bool GLMachine::isTexCoordEnabled() const { return texCoordEnable_; }

		void glBegin(GLenum mode)
		{
			GLMachine::machine().glBegin(mode);
		}
		void glEnd()
		{
			GLMachine::machine().glEnd();
		}
		void glVertex2f(GLfloat x, GLfloat y)
		{
			glVertex3f(x, y, 0.0f);
		}
		void glVertex3f(GLfloat	x, GLfloat y,GLfloat z)
		{
			GLMachine::machine().addVertex(x, y, z);
		}
		void glTexCoord2f(GLfloat s, GLfloat t)
		{
			GLMachine::machine().setTexCoord(s, t);
		}

#if _IPHONE_
#pragma mark gl state container (state save/load)
#endif
		GLStateContainer::GLStateContainer()
		: saved_(false)
		{
		}
		void GLStateContainer::save()
		{
			assert(saved_ == false);
			saved_ = !saved_;

			vertexArrayEnable_ = glIsEnabled(GL_VERTEX_ARRAY);
			colorArrayEnable_ = glIsEnabled(GL_COLOR_ARRAY);
			texCoordArrayEnable_ = glIsEnabled(GL_TEXTURE_COORD_ARRAY);
			normalArrayEnable_ = glIsEnabled(GL_NORMAL_ARRAY);

			lightingEnable_ = glIsEnabled(GL_LIGHTING);
			tex2dEnable_ = glIsEnabled(GL_TEXTURE_2D);
			cullEnable_ = glIsEnabled(GL_CULL_FACE);
		}
		void GLStateContainer::restore()
		{
			assert(saved_ == true);
			saved_ = !saved_;

			if(vertexArrayEnable_)
				glEnableClientState(GL_VERTEX_ARRAY);
			else
				glDisableClientState(GL_VERTEX_ARRAY);

			if(colorArrayEnable_)
				glEnableClientState(GL_COLOR_ARRAY);
			else
				glDisableClientState(GL_COLOR_ARRAY);

			if(texCoordArrayEnable_)
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			else
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);

			if(normalArrayEnable_)
				glEnableClientState(GL_NORMAL_ARRAY);
			else
				glDisableClientState(GL_NORMAL_ARRAY);

			if(lightingEnable_)
				glEnable(GL_LIGHTING);
			else
				glDisable(GL_LIGHTING);

			if(tex2dEnable_)
				glEnable(GL_TEXTURE_2D);
			else
				glDisable(GL_TEXTURE_2D);

			if(cullEnable_)
				glEnable(GL_CULL_FACE);
			else
				glDisable(GL_CULL_FACE);

		}

#if _IPHONE_
#pragma mark glut model
#endif
		void glutSolidCube(GLfloat size)
		{
			GLfloat color[4];
			glGetFloatv(GL_CURRENT_COLOR, color);
			matsu::vec4 colorVec(color);

			sora::SolidCube cube(size, size, size, colorVec);
			drawModel(&cube);
		}
		void glutWireCube(GLfloat size)
		{
			GLfloat color[4];
			glGetFloatv(GL_CURRENT_COLOR, color);
			matsu::vec4 colorVec(color);

			sora::WireCube cube(size, size, size, colorVec);
			drawModel(&cube);
		}

		void glutSolidSphere(GLfloat radius, GLint slices, GLint stacks)
		{
			//set color
			GLfloat c[4];
			glGetFloatv(GL_CURRENT_COLOR, c);
			matsu::vec4 color(c);

			sora::SolidSphere sphere(radius, slices, stacks, color);
			drawModel(&sphere);
		}
		void glutWireSphere(GLfloat radius, GLint slices, GLint stacks)
		{
			//get color
			GLfloat color[4];
			glGetFloatv(GL_CURRENT_COLOR, color);
			matsu::vec4 colorVec(color);

			sora::WireSphere sphere(radius, slices, stacks, colorVec);
			drawModel(&sphere);
		}

		void glutSolidCone(GLfloat base, GLfloat height, GLint slices, GLint stacks)
		{
			GLfloat c[4];
			glGetFloatv(GL_CURRENT_COLOR, c);
			matsu::vec4 color(c);

			sora::SolidCone cone(base, height, slices, stacks, color);
			drawModel(&cone);
		}
		void glutWireCone(GLfloat base, GLfloat height, GLint slices, GLint stacks)
		{
			//get color
			GLfloat color[4];
			glGetFloatv(GL_CURRENT_COLOR, color);
			matsu::vec4 colorVec(color);

			sora::WireCone cone(base, height, slices, stacks, colorVec);
			drawModel(&cone);
		}

		void drawModel(const sora::Model *model)
		{
			GLStateContainer container;
			container.save();

			//면의 방향이 정리되어있지 않아서 culling은 일단 끈다
			glDisable(GL_CULL_FACE);

			const vector<sora::DrawCommand> cmdList = model->getDrawCommand();
			vector<sora::DrawCommand>::const_iterator it;
			for(it = cmdList.begin() ; it != cmdList.end() ; it++)
			{
				const sora::DrawCommand &cmd = *it;
				const sora::VertexPointer &ptr = cmd.pointer();

				glEnableClientState(GL_VERTEX_ARRAY);
				glVertexPointer(3, GL_FLOAT, ptr.stride(), cmd.pointer().vertex());

				if(ptr.useColorPointer())
				{
					glEnableClientState(GL_COLOR_ARRAY);
					glColorPointer(4, GL_FLOAT, ptr.stride(), ptr.color());
				}
				else
				{
					glDisableClientState(GL_COLOR_ARRAY);
				}

				if(ptr.useNormalPointer())
				{
					glEnableClientState(GL_NORMAL_ARRAY);
					glNormalPointer(GL_FLOAT, ptr.stride(), cmd.pointer().normal());
				}
				else
				{
					glDisableClientState(GL_NORMAL_ARRAY);
				}

				if(ptr.useTexCoordPointer())
				{
					glEnable(GL_TEXTURE_2D);
					glEnableClientState(GL_TEXTURE_COORD_ARRAY);
					glTexCoordPointer(2, GL_FLOAT, ptr.stride(), cmd.pointer().texCoord());
				}
				else
				{
					glDisable(GL_TEXTURE_2D);
					glDisableClientState(GL_TEXTURE_COORD_ARRAY);
				}
				glDrawElements(cmd.mode(), cmd.count(), cmd.type(), cmd.indices());
			}

			container.restore();
		}

#if _IPHONE_
#pragma mark camera
#endif
		void gluPerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar)
		{
			GLfloat xmin, xmax, ymin, ymax;

			ymax = static_cast<float>(zNear * tan(fovy * M_PI / 360.0));
			ymin = -ymax;

			xmin = ymin * aspect;
			xmax = ymax * aspect;

			glFrustumf(xmin, xmax, ymin, ymax, zNear, zFar);
		}
		void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez,
					   GLfloat centerx, GLfloat centery, GLfloat centerz,
					   GLfloat upx, GLfloat upy, GLfloat upz)
		{
			GLfloat m[16];
			GLfloat x[3], y[3], z[3];
			GLfloat mag;

			/* Make rotation matrix */

			/* Z vector */
			z[0] = eyex - centerx;
			z[1] = eyey - centery;
			z[2] = eyez - centerz;
			mag = sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]);
			if (mag) {          /* mpichler, 19950515 */
				z[0] /= mag;
				z[1] /= mag;
				z[2] /= mag;
			}

			/* Y vector */
			y[0] = upx;
			y[1] = upy;
			y[2] = upz;

			/* X vector = Y cross Z */
			x[0] = y[1] * z[2] - y[2] * z[1];
			x[1] = -y[0] * z[2] + y[2] * z[0];
			x[2] = y[0] * z[1] - y[1] * z[0];

			/* Recompute Y = Z cross X */
			y[0] = z[1] * x[2] - z[2] * x[1];
			y[1] = -z[0] * x[2] + z[2] * x[0];
			y[2] = z[0] * x[1] - z[1] * x[0];

			/* mpichler, 19950515 */
			/* cross product gives area of parallelogram, which is < 1.0 for
			 * non-perpendicular unit-length vectors; so normalize x, y here
			 */

			mag = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
			if (mag) {
				x[0] /= mag;
				x[1] /= mag;
				x[2] /= mag;
			}

			mag = sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]);
			if (mag) {
				y[0] /= mag;
				y[1] /= mag;
				y[2] /= mag;
			}

#define M(row,col)  m[col*4+row]
			M(0, 0) = x[0];
			M(0, 1) = x[1];
			M(0, 2) = x[2];
			M(0, 3) = 0.0;
			M(1, 0) = y[0];
			M(1, 1) = y[1];
			M(1, 2) = y[2];
			M(1, 3) = 0.0;
			M(2, 0) = z[0];
			M(2, 1) = z[1];
			M(2, 2) = z[2];
			M(2, 3) = 0.0;
			M(3, 0) = 0.0;
			M(3, 1) = 0.0;
			M(3, 2) = 0.0;
			M(3, 3) = 1.0;
#undef M
			glMultMatrixf(m);

			/* Translate Eye to Origin */
			glTranslatef(-eyex, -eyey, -eyez);

		}
	}
#endif
}


